package com.baishui.beans.factory.support;

import com.baishui.beans.BeanDefinition;
import com.baishui.beans.BeansException;
import com.baishui.beans.PropertyValue;
import com.baishui.beans.SimpleTypeConverter;
import com.baishui.beans.factory.BeanFactoryAware;
import com.baishui.beans.factory.NoSuchBeanDefinitionException;
import com.baishui.beans.factory.config.BeanPostProcessor;
import com.baishui.beans.factory.config.DependencyDescriptor;
import com.baishui.beans.factory.config.InstantiationAwareBeanPostProcessor;
import com.baishui.beans.factory.exception.BeanCreationException;
import com.baishui.context.support.AbstractApplicationContext;
import com.baishui.context.support.ConstructorResolver;
import com.baishui.utils.ClassUtils;

import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @Description: 非常重要的类信息
 * （1）承担 ConfigurableBeanFactory
 * （2）注册 bean的定义信息 BeanDefinitionRegistry
 * （3）生成并保存单例bean的作用
 * @Author: baishui
 * @Create: 2020-08-24 21:11
 **/
public class DefaultBeanFactory extends AbstractBeanFactory
        implements BeanDefinitionRegistry {

    private static final String ID_ATTRIBUTE = "id";

    private static final String CLASS_ATTRIBUTE = "class";

    private ClassLoader classLoader;

    /**
     *  保存bean的定义信息
     */
    private final Map<String,BeanDefinition> beanDefinitionMap
            = new ConcurrentHashMap<String, BeanDefinition>();

    private List<BeanPostProcessor> beanPostProcessors = new ArrayList<BeanPostProcessor>();


    public DefaultBeanFactory() {
    }

    public BeanDefinition getBeanDefinition(String beanID) {
        return this.beanDefinitionMap.get(beanID);
    }

    /**
     * v2：新增方法
     * @see BeanDefinitionRegistry#registerBeanDefinition(String, BeanDefinition) v2:ADD
     * @param beanID
     * @param bd
     */
    public void registerBeanDefinition(String beanID, BeanDefinition bd) {
        this.beanDefinitionMap.put(beanID,bd);
    }

    /**
     * 只有在get的时候才进行bean的实例化
     * @param beanID
     * @return
     */
    public Object getBean(String beanID) {
        BeanDefinition bd = this.getBeanDefinition(beanID);
        if(bd == null){
            return null;
        }

        /**
         * 判断当前的bean是否为单例的
         */
        if(bd.isSingleton()){
            Object bean = this.getSingleton(beanID);

            if(bean == null){
                bean = createBean(bd);
                this.registerSingleton(beanID, bean);
            }

            return bean;
        }
        return createBean(bd);
    }

    /**
     * 真正实例化bean的步骤
     * @param bd bean的定义信息
     * @return
     */
    public Object createBean(BeanDefinition bd) {
        /**
         * 实例化bean的操作
         */
        Object bean = instantiateBean(bd);

        /**
         * 设置bean的属性信息
         */
        populateBean(bd, bean);

        /**
         * 初始化bean的操作
         */
        bean = initializeBean(bd,bean);

        return bean;
    }

    protected Object initializeBean(BeanDefinition bd, Object bean)  {
        invokeAwareMethods(bean);
        //Todo，调用Bean的init方法，暂不实现
        if(!bd.isSynthetic()){
            return applyBeanPostProcessorsAfterInitialization(bean,bd.getID());
        }
        return bean;
    }
    public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName)
            throws BeansException {

        Object result = existingBean;
        for (BeanPostProcessor beanProcessor : getBeanPostProcessors()) {
            result = beanProcessor.afterInitialization(result, beanName);
            if (result == null) {
                return result;
            }
        }
        return result;
    }

    private void invokeAwareMethods(final Object bean) {
        if (bean instanceof BeanFactoryAware) {
            ((BeanFactoryAware) bean).setBeanFactory(this);
        }
    }

    private Object instantiateBean(BeanDefinition bd) {
        /**
         * 如果存在多个构造器函数
         */
        if(bd.hasConstructorArgumentValues()){
            ConstructorResolver resolver = new ConstructorResolver(this);
            return resolver.autowireConstructor(bd);
        } else {
             ClassLoader cl = this.getBeanClassLoader();
             String beanClassName = bd.getBeanClassName();
             try {
                 Class<?> clz = cl.loadClass(beanClassName);
                /**
                 * 默认无参构造器
                 */
                return clz.newInstance();
            } catch (Exception e) {
                throw new BeanCreationException("create bean for "+ beanClassName +" failed",e);
            }
        }
    }

    protected void populateBean(BeanDefinition bd, Object bean){

        for(BeanPostProcessor processor : this.getBeanPostProcessors()){
            if(processor instanceof InstantiationAwareBeanPostProcessor){
                ((InstantiationAwareBeanPostProcessor)processor).postProcessPropertyValues(bean, bd.getID());
            }
        }

        List<PropertyValue> pvs = bd.getPropertyValues();

        if (pvs == null || pvs.isEmpty()) {
            return;
        }

        BeanDefinitionValueResolver valueResolver = new BeanDefinitionValueResolver(this);
        SimpleTypeConverter converter = new SimpleTypeConverter();
        try{

            /**
             * javabean
             */
            BeanInfo beanInfo = Introspector.getBeanInfo(bean.getClass());
            /**
             * 属性的描述器
             */
            PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors();

            for (PropertyValue pv : pvs){
                String propertyName = pv.getName();
                Object originalValue = pv.getValue();
                Object resolvedValue = valueResolver.resolveValueIfNecessary(originalValue);

                for (PropertyDescriptor pd : pds) {
                    if(pd.getName().equals(propertyName)){
                        Object convertedValue = converter.convertIfNecessary(resolvedValue, pd.getPropertyType());
                        pd.getWriteMethod().invoke(bean, convertedValue);
                        break;
                    }
                }
            }
        }catch(Exception ex){
            throw new BeanCreationException("Failed to obtain BeanInfo for class [" + bd.getBeanClassName() + "]", ex);
        }
    }

    public void setBeanClassLoader(ClassLoader beanClassLoader) {
        this.classLoader = beanClassLoader==null ? ClassUtils.getDefaultClassLoader():beanClassLoader;
    }

    /**
     * @see AbstractApplicationContext#getBeanClassLoader()
     * TODO 存在相同的代码
     * @return
     */
    public ClassLoader getBeanClassLoader() {
        return (this.classLoader != null ? this.classLoader : ClassUtils.getDefaultClassLoader());
    }


    public void addBeanPostProcessor(BeanPostProcessor postProcessor){
        this.beanPostProcessors.add(postProcessor);
    }

    public List<BeanPostProcessor> getBeanPostProcessors() {
        return this.beanPostProcessors;
    }

    public Object resolveDependency(DependencyDescriptor descriptor) {
        Class<?> typeToMatch = descriptor.getDependencyType();
        for(BeanDefinition bd: this.beanDefinitionMap.values()){
            //确保BeanDefinition 有Class对象
            resolveBeanClass(bd);
            Class<?> beanClass = bd.getBeanClass();
            if(typeToMatch.isAssignableFrom(beanClass)){
                return this.getBean(bd.getID());
            }
        }
        return null;
    }
    public void resolveBeanClass(BeanDefinition bd) {
        if(bd.hasBeanClass()){
            return;
        } else{
            try {
                bd.resolveBeanClass(this.getBeanClassLoader());
            } catch (ClassNotFoundException e) {
                throw new RuntimeException("can't load class:"+bd.getBeanClassName());
            }
        }
    }

    public Class<?> getType(String name) throws NoSuchBeanDefinitionException {
        BeanDefinition bd = this.getBeanDefinition(name);
        if(bd == null){
            throw new NoSuchBeanDefinitionException(name);
        }
        resolveBeanClass(bd);
        return bd.getBeanClass();
    }

    public List<Object> getBeansByType(Class<?> type) {
        List<Object> result = new ArrayList<Object>();
        List<String> beanIDs = this.getBeanIDsByType(type);
        for(String beanID : beanIDs){
            result.add(this.getBean(beanID));
        }
        return result;
    }

    private List<String> getBeanIDsByType(Class<?> type){
        List<String> result = new ArrayList<String>();
        for(String beanName :this.beanDefinitionMap.keySet()){
            if(type.isAssignableFrom(this.getType(beanName))){
                result.add(beanName);
            }
        }
        return result;
    }
}
